﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;

namespace FileTransfer.BlockCipher
{
    public class GOST_89 : FeistelCipher
    {
        //=========CIPHER CHARACTERICS===========//
        private static readonly uint ROUNDS = 0x20;
        private static readonly uint SUB = 0x4;
        private static readonly uint SHIFT = 11;
        private static readonly uint EIGHT = 8;
        private static byte[,] S = {
        { 4,10, 9, 2,13, 8, 0,14, 6,11, 1,12, 7,15, 5, 3},
        {14,11, 4,12, 6,13,15,10, 2, 3, 8, 1, 0, 7, 5, 9},
        { 5, 8, 1,13,10, 3, 4, 2,14,15,12, 7, 6, 0, 9,11},
        { 7,13,10, 1, 0, 8, 9,15,14, 4, 6,12,11, 2, 5, 3},
        { 6,12, 7, 1, 5,15,13, 8, 4,10, 9,14, 0, 3,11, 2},
        { 4,11,10, 0, 7, 2, 1,13, 3, 6, 8, 5, 9,12,15,14},
        {13,11, 4, 1, 3,15, 5, 9, 0,10,14, 7, 6, 8, 2,12},
        { 1,15,13, 0, 5, 7,10, 4, 9, 2, 3,14, 6,11, 8,12}
        };


        //=============KEY ELEMENTS==============//

        public GOST_89(byte[] key): base(key)
        {
        }


        protected override Block EncipherBlock(Block item)
        {
            Block x = item;
            for (uint j = 0; j < ROUNDS; ++j)
            {
                x = FeistelPermutation(x, j, CipherDirection.ENCIPHER);
            }
            x.Swap();
            return x;
        }

        protected override Block DecipherBlock(Block item)
        {
            Block x = item;
            for (uint j = 0; j < ROUNDS; ++j)
            {
                x = FeistelPermutation(x, j, CipherDirection.DECIPHER);
            }
            x.Swap();
            return x;
        }

        protected override uint FeistelFunction(uint a, uint k)
        {
            uint sum = (a + k);
            uint res = 0;
            for (uint i = 0; i < EIGHT; ++i)
            {
                //find the i-th block of SUB bytes
                uint state = (sum << (byte)(sizeof(uint) * 8 - SUB * (i + 1))) >> (byte)(sizeof(uint) * 8 - SUB);
                state = S[i, state];
                res += state << (byte)(SUB * i);
            }
            //cycle shift
            res = (res << (byte)SHIFT) + (res >> (byte)(sizeof(uint) * 8 - SHIFT));
            return res;
        }

        protected override Block FeistelPermutation(Block x, uint iter, CipherDirection dir)
        {
            if (!(iter < ROUNDS)) return x;
            switch (dir)
            {
                case CipherDirection.ENCIPHER:
                    if (iter < ROUNDS - keyLength)
                        iter %= keyLength;
                    else
                        iter = keyLength - iter % keyLength - 1;
                    break;
                case CipherDirection.DECIPHER:
                    if (iter < keyLength)
                        iter %= keyLength;
                    else
                        iter = keyLength - iter % keyLength - 1;
                    break;
            }
            Debug.Assert(iter < keyLength);
            Block res;
            res.L = x.R ^ FeistelFunction(x.L, key[iter]);
            res.R = x.L;
            return res;
        }
    }
}